package sys

import (
	"compress/gzip"
	"fmt"
	"go-monitor/agent/g"
	"go-monitor/common/utility"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"time"

	"github.com/kardianos/osext"
)

type MeteredReader struct {
	rd        io.ReadCloser
	totalSize int64
	progress  chan int
	totalRead int64
	ticks     int64
}

func (m *MeteredReader) Close() error {
	return m.rd.Close()
}

func (m *MeteredReader) Read(b []byte) (n int, err error) {
	chunkSize := (m.totalSize / 100) + 1
	lenB := int64(len(b))

	var nChunk int
	for start := int64(0); start < lenB; start += int64(nChunk) {
		end := start + chunkSize
		if end > lenB {
			end = lenB
		}

		nChunk, err = m.rd.Read(b[start:end])

		n += nChunk
		m.totalRead += int64(nChunk)

		if m.totalRead > (m.ticks * chunkSize) {
			m.ticks += 1
			// try to send on channel, but don't block if it's full
			select {
			case m.progress <- int(m.ticks + 1):
			default:
			}

			// give the progress channel consumer a chance to run
			runtime.Gosched()
		}

		if err != nil {
			return
		}
	}

	return
}

type RoundTripper struct {
	RoundTripFn func(*http.Request) (*http.Response, error)
}

func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	return rt.RoundTripFn(r)
}

type Download struct {
	HttpClient *http.Client

	Path string

	Progress  chan (int)
	Method    string
	Url       string
	Available bool
}

func NewDownload(url string) *Download {
	return &Download{
		HttpClient: new(http.Client),
		Progress:   make(chan int),
		Method:     "GET",
		Url:        url,
	}
}

func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) {
	req, err := http.NewRequest(d.Method, d.Url, nil)
	if err != nil {
		return
	}

	// we have to add headers like this so they get used across redirects
	trans := d.HttpClient.Transport
	if trans == nil {
		trans = http.DefaultTransport
	}

	d.HttpClient.Transport = &RoundTripper{
		RoundTripFn: func(r *http.Request) (*http.Response, error) {
			// add header for download continuation
			if offset > 0 {
				r.Header.Add("Range", fmt.Sprintf("%d-", offset))
			}
			r.Header.Add("Accept-Encoding", "gzip")

			return trans.RoundTrip(r)
		},
	}
	return d.HttpClient.Do(req)
}

func (d *Download) Get() (err error) {
	resp, err := d.sharedHttp(0)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case 200, 206:
		d.Available = true

	case 204:
		return
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}
	clength := resp.ContentLength

	rd := resp.Body

	if clength > 0 {
		rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress}
	}

	if resp.Header.Get("Content-Encoding") == "gzip" {
		rd, err = gzip.NewReader(rd)
		if err != nil {
			return
		}
	}
	fp, err := os.Create(g.Root + `/agent_new`)
	if err != nil {
		g.Logger.Error("创建agent_new", err)
		return
	}

	_, err = io.Copy(fp, rd)
	if err != nil {
		return
	}

	return
}

func SanityCheck() (err error) {
	thisExecPath, err := osext.Executable()
	if err != nil {
		return
	}

	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	fp.Close()

	_ = os.Remove(newExecPath)
	return
}

func (d *Download) GetAndUpdate() (err error, errRecover error) {
	if err = SanityCheck(); err != nil {
		close(d.Progress)
		return
	}
	if err = d.Get(); err != nil || !d.Available {
		return
	}

	pidf := g.Root + "/var/pid.txt"
	pid, _ := utility.ReadFileAsBytes(pidf)
	cmd := exec.Command("/bin/sh", "-c", "kill -9 "+string(pid))
	err = cmd.Start()
	g.Logger.Error("agent kill进程", string(pid))
	if err != nil {
		g.Logger.Error("agent kill进程异常", err)
	}

	return
}

func FromUrl(url string) (err error, errRecover error) {
	return NewDownload(url).GetAndUpdate()
}

func AutoUpdate() {
	go func() {
		for {
			err, _ := FromUrl("http://" + g.Config().HTTPServer + "/update?v=" + g.VERSION)
			if err != nil {
				g.Logger.Error("Update failed: %v", err)
			}
			time.Sleep(300 * time.Second)
		}
	}()
}

func UpdateNotice() {
	defer func() {
		if err := recover(); err != nil {
			g.Logger.Error("Update:%s", err)
		}
	}()

	pidf := g.Root + "/var/pid.txt"
	if !utility.PathExists(g.Root + "/var/") {
		os.MkdirAll(g.Root+"/var/", os.ModePerm)
	}

	if utility.PathExists(pidf) {
		pids, _ := utility.ReadFileAsBytes(pidf)
		pid, _ := strconv.Atoi(string(pids))
		if pid != os.Getpid() && pid != 0 {
			c := exec.Command("/bin/sh", "-c", fmt.Sprintf("kill -9 %s", pid))
			err := c.Start()
			if err != nil {
				g.Logger.Error("err is", err)
			}
		}
	}
	ioutil.WriteFile(pidf, []byte(strconv.Itoa(os.Getpid())), os.ModePerm)
	// time.Sleep(3 * time.Second)
}
